<?php
// +----------------------------------------------------------------------
// | 控制器 - APP
// +----------------------------------------------------------------------
declare(strict_types=1);

namespace app\controller\app;

use app\BaseController;
use app\model\SystemUsersModel;
use app\service\JwtTokenService;
use app\service\RefreshTokenService;
use app\service\SmsAuthService;
use app\validate\AccessUsersValidate;
use think\exception\ValidateException;
use think\Response;

/**
 * 访问控制
 *
 * Class Access
 * @package app\controller\app
 */
class Access extends BaseController
{

    /**
     * 登录返回
     * 
     * @param SystemUsersModel $user 用户信息
     * @param string $deviceId 设备编码
     */
    private function login(SystemUsersModel $user, string $deviceId): Response
    {
        // 写入用户信息
        $this->request->setUser($user);
        // 返回token
        $refreshToken = RefreshTokenService::geneUserToken($user->id, $deviceId);
        return $this->response([
            'accessToken' => JwtTokenService::issuingToken([
                'uid' => $user->id,
            ]),
            'refreshToken' => $refreshToken,
            'expires' => JwtTokenService::getTokenExpiresTime()->format('Y-m-d H:i:s')
        ]);
    }

    /**
     * @OA\Post(
     *   path="/api/app/access/login/phone",
     *   tags={"访问控制"},
     *   summary="用户登录（手机号）",
     *   description="用户手机号登录操作，设备编号是一个客户端管理的字符串，目前用于刷新token",
     *   @OA\RequestBody(
     *   @OA\MediaType(
     *     mediaType="content-type/json",
     *       @OA\Schema(
     *         @OA\Property(description="手机号", property="phone", type="string", example="13800000000"),
     *         @OA\Property(description="验证码", property="code", type="string", example=""),
     *         @OA\Property(description="设备编号", property="deviceId", type="string", example="KcFsYHYSlSx2emwobCGT7hULspqxiUX0"),
     *         required={"phone","code", "deviceId"})
     *     )
     *   ),
     *   @OA\Response(
     *     response="200",
     *     description="Success",
     *     @OA\JsonContent(
     *       type="object",
     *       @OA\Property(property="success", type="boolean", example=true),
     *       @OA\Property(property="message", type="string", example="操作成功"),
     *       @OA\Property(
     *         property="data",
     *         type="object",
     *         @OA\Property(property="accessToken", type="string"),
     *         @OA\Property(property="refreshToken", type="string"),
     *         @OA\Property(property="expires", type="string", example="2025-01-01 12:00:00")
     *       )
     *     )
     *   )
     * )
     * @return Response
     */
    public function loginByPhone(): Response
    {
        $query = [
            'phone' => $this->request->post('phone'),
            'code' => $this->request->post('code'),
            'deviceId' => $this->request->post('deviceId')
        ];
        try {
            // 字段验证
            validate(AccessUsersValidate::class)->scene('phoneLogin')->check($query);
            // 短信验证
            SmsAuthService::verifyPhoneSms($query['phone'], $query['code']);
            // 登录操作
            $user = SystemUsersModel::queryOrder()->where('phone', $query['phone'])->find();
            if (!$user) {
                abort(200, '该号码尚未注册');
            }
            return $this->login($user, $query['deviceId']);
        } catch (ValidateException $e) {
            // 验证失败 输出错误信息
            abort(200, $e->getError());
        }
    }

    /**
     * @OA\Post(
     *   path="/api/app/access/login/account",
     *   tags={"访问控制"},
     *   summary="用户登录（账密）",
     *   description="用户账号密码登录操作，设备编号是一个客户端管理的字符串，目前用于刷新token",
     *   @OA\RequestBody(
     *   @OA\MediaType(
     *     mediaType="content-type/json",
     *       @OA\Schema(
     *         @OA\Property(description="帐号", property="passport", type="string", example="common"),
     *         @OA\Property(description="密码", property="password", type="string", example="admin@123456"),
     *         @OA\Property(description="设备编号", property="deviceId", type="string", example="KcFsYHYSlSx2emwobCGT7hULspqxiUX0"),
     *         required={"passport","password", "deviceId"})
     *     )
     *   ),
     *   @OA\Response(
     *     response="200",
     *     description="Success",
     *     @OA\JsonContent(
     *       type="object",
     *       @OA\Property(property="success", type="boolean", example=true),
     *       @OA\Property(property="message", type="string", example="操作成功"),
     *       @OA\Property(
     *         property="data",
     *         type="object",
     *         @OA\Property(property="accessToken", type="string"),
     *         @OA\Property(property="refreshToken", type="string"),
     *         @OA\Property(property="expires", type="string", example="2025-01-01 12:00:00")
     *       )
     *     )
     *   )
     * )
     * @return Response
     */
    public function loginByAccount(): Response
    {
        $query = [
            'passport' => $this->request->post('passport'),
            'password' => $this->request->post('password'),
            'deviceId' => $this->request->post('deviceId')
        ];
        try {
            // 字段验证
            validate(AccessUsersValidate::class)->scene('login')->check($query);
            // 登录操作
            $user = SystemUsersModel::queryOrder()->where('username', $query['passport'])->find();
            if (!$user) {
                abort(200, '账号或密码错误');
            }
            if (!verifyPassword($query['password'], $user->password)) {
                abort(200, '账号或密码错误');
            }
            return $this->login($user, $query['deviceId']);
        } catch (ValidateException $e) {
            // 验证失败 输出错误信息
            abort(200, $e->getError());
        }
    }

    /**
     * @OA\Post(
     *   path="/api/app/access/login/send-sms",
     *   tags={"访问控制"},
     *   summary="发送登录短信",
     *   description="注：本API目前为模拟发送，为方便调试请求会返回验证码数据",
     *   @OA\RequestBody(
     *   @OA\MediaType(
     *     mediaType="content-type/json",
     *       @OA\Schema(
     *         @OA\Property(description="手机号", property="phone", type="string", example="13800000000"),
     *         required={"phone"})
     *     )
     *   ),
     *   @OA\Response(
     *     response="200",
     *     description="Success",
     *     @OA\JsonContent(
     *       type="object",
     *       @OA\Property(property="success", type="boolean", example=true),
     *       @OA\Property(property="message", type="string", example="操作成功"),
     *       @OA\Property(
     *         property="data",
     *         type="object",
     *         @OA\Property(property="code", type="string"),
     *       )
     *     )
     *   )
     * )
     * @return Response
     */
    public function sendAuthSms(): Response
    {
        $query = [
            'phone' => $this->request->post('phone'),
        ];
        try {
            // 字段验证
            validate(AccessUsersValidate::class)->scene('phone')->check(['phone' => $query['phone']]);
            // 发送短信
            $code = SmsAuthService::genePhoneSms($query['phone']);
            return $this->response([
                'code' => $code
            ]);
        } catch (ValidateException $e) {
            // 验证失败 输出错误信息
            abort(200, $e->getError());
        }
    }

    /**
     * @OA\Post(
     *   path="/api/app/access/refresh-token",
     *   tags={"访问控制"},
     *   summary="刷新Token",
     *   description="刷新并重新获取新的accessToken和refreshToken，原token在有效期内仍然生效",
     *   @OA\RequestBody(
     *   @OA\MediaType(
     *     mediaType="content-type/json",
     *       @OA\Schema(
     *         @OA\Property(description="用户ID", property="uid", type="int", example=2),
     *         @OA\Property(description="设备编号", property="deviceId", type="string", example="KcFsYHYSlSx2emwobCGT7hULspqxiUX0"),
     *         @OA\Property(description="refreshToken", property="refreshToken", type="string", example=""),
     *         required={""})
     *     )
     *   ),
     *   @OA\Response(
     *     response="200",
     *     description="Success",
     *     @OA\JsonContent(
     *       type="object",
     *       @OA\Property(property="success", type="boolean", example=true),
     *       @OA\Property(property="message", type="string", example="操作成功"),
     *       @OA\Property(
     *         property="data",
     *         type="object",
     *         @OA\Property(property="accessToken", type="string"),
     *         @OA\Property(property="refreshToken", type="string"),
     *         @OA\Property(property="expires", type="string", example="2025-01-01 12:00:00")
     *       )
     *     )
     *   )
     * )
     * @return Response
     */
    public function refreshToken(): Response
    {
        $query = [
            'uid' => $this->request->post('uid'),
            'deviceId' => $this->request->post('deviceId'),
            'refreshToken' => $this->request->post('refreshToken'),
        ];
        try {
            // 字段验证
            validate(AccessUsersValidate::class)->scene('refresh')->check($query);
            // 获取新的token
            $token = RefreshTokenService::refreshUserToken(
                uid: $query['uid'],
                salt: $query['deviceId'],
                token: $query['refreshToken']
            );
            if (!$token) {
                abort(401, '认证失败！请重新登录');
            }
            return $this->response([
                'accessToken' => JwtTokenService::issuingToken([
                    'uid' => $query['uid'],
                ]),
                'refreshToken' => $token,
                'expires' => JwtTokenService::getTokenExpiresTime()->format('Y-m-d H:i:s')
            ]);
        } catch (ValidateException $e) {
            // 验证失败 输出错误信息
            abort(200, $e->getError());
        }
    }

    /**
     * @OA\Get(
     *   path="/api/app/access/info",
     *   tags={"访问控制"},
     *   summary="获取用户信息",
     *   description="获取当前已登录用户信息",
     *   security={{"bearerAuth":{}}},
     *   @OA\Response(response=401, description="未携带TOKEN或已过期"),
     *   @OA\Response(
     *     response="200",
     *     description="Success",
     *     @OA\JsonContent(
     *       type="object",
     *       @OA\Property(property="success", type="boolean", example=true),
     *       @OA\Property(property="message", type="string", example="操作成功"),
     *       @OA\Property(
     *         property="data",
     *         type="object",
     *         @OA\Property(property="uid", type="integer", example=1),
     *         @OA\Property(property="username", type="string", example="admin"),
     *         @OA\Property(property="nickname", type="string", example="管理员"),
     *         @OA\Property(property="phone", type="string", example="13700000000"),
     *         @OA\Property(property="email", type="string", example="vas-admin@faker.com"),
     *         @OA\Property(property="remark", type="string", example="个人备注")
     *       )
     *     )
     *   )
     * )
     * @return Response
     */
    public function getUserInfo(): Response
    {
        $user = $this->request->getUserInfo();
        return $this->response([
            'uid' => $user['id'],
            'username' => $user['username'],
            'nickname' => $user['nickname'],
            'phone' => $user['phone'],
            'email' => $user['email'],
            'remark' => $user['remark'],
        ]);
    }
}
